#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import getpass
import hashlib
import logging
import sys

from oslo_utils import timeutils
# NOTE(stevemar): do not remove positional. We need this to stay for a while
# since versions of auth_token require it here.
from positional import positional  # noqa
import six

from keystoneclient import exceptions


logger = logging.getLogger(__name__)


def find_resource(manager, name_or_id):
    """Helper for the _find_* methods."""
    # first try the entity as a string
    try:
        return manager.get(name_or_id)
    except (exceptions.NotFound):  # nosec(cjschaef): try to find 'name_or_id'
        # as a six.binary_type instead
        pass

    # finally try to find entity by name
    try:
        if isinstance(name_or_id, six.binary_type):
            name_or_id = name_or_id.decode('utf-8', 'strict')
        return manager.find(name=name_or_id)
    except exceptions.NotFound:
        msg = ("No %s with a name or ID of '%s' exists." %
               (manager.resource_class.__name__.lower(), name_or_id))
        raise exceptions.CommandError(msg)
    except exceptions.NoUniqueMatch:
        msg = ("Multiple %s matches found for '%s', use an ID to be more"
               " specific." % (manager.resource_class.__name__.lower(),
                               name_or_id))
        raise exceptions.CommandError(msg)


def unauthenticated(f):
    """Add 'unauthenticated' attribute to decorated function.

    Usage::

        @unauthenticated
        def mymethod(f):
            ...
    """
    f.unauthenticated = True
    return f


def isunauthenticated(f):
    """Check if function requires authentication.

    Checks to see if the function is marked as not requiring authentication
    with the @unauthenticated decorator.

    Returns True if decorator is set to True, False otherwise.
    """
    return getattr(f, 'unauthenticated', False)


def hash_signed_token(signed_text, mode='md5'):
    hash_ = hashlib.new(mode)
    hash_.update(signed_text)
    return hash_.hexdigest()


def prompt_user_password():
    """Prompt user for a password.

    Prompt for a password if stdin is a tty.
    """
    password = None

    # If stdin is a tty, try prompting for the password
    if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
        # Check for Ctl-D
        try:
            password = getpass.getpass('Password: ')
        except EOFError:  # nosec(cjschaef): return password, which is None if
            # password was not found
            pass

    return password


def prompt_for_password():
    """Prompt user for password if not provided.

    Prompt is used so the password doesn't show up in the
    bash history.
    """
    if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()):
        # nothing to do
        return

    while True:
        try:
            new_passwd = getpass.getpass('New Password: ')
            rep_passwd = getpass.getpass('Repeat New Password: ')
            if new_passwd == rep_passwd:
                return new_passwd
        except EOFError:
            return


_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'


def isotime(at=None, subsecond=False):
    """Stringify time in ISO 8601 format."""
    # Python provides a similar instance method for datetime.datetime objects
    # called isoformat(). The format of the strings generated by isoformat()
    # have a couple of problems:
    # 1) The strings generated by isotime are used in tokens and other public
    #    APIs that we can't change without a deprecation period. The strings
    #    generated by isoformat are not the same format, so we can't just
    #    change to it.
    # 2) The strings generated by isoformat do not include the microseconds if
    #    the value happens to be 0. This will likely show up as random failures
    #    as parsers may be written to always expect microseconds, and it will
    #    parse correctly most of the time.
    if not at:
        at = timeutils.utcnow()
    st = at.strftime(_ISO8601_TIME_FORMAT
                     if not subsecond
                     else _ISO8601_TIME_FORMAT_SUBSECOND)
    tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
    st += ('Z' if tz == 'UTC' else tz)
    return st


def strtime(at=None):
    at = at or timeutils.utcnow()
    return at.strftime(timeutils.PERFECT_TIME_FORMAT)
